package ArithmeticObjects;

import java.util.InputMismatchException;
import Utils.ByteTree;
import ArithUtils.*;
import CryptoPrim.PRG;

/**
 * @author Shir Peleg
 * This class represents a verificatum Group, contains a general convertible group,
 * and by its running time type can figure its parameters. 
 */

public class VerfGroup{

	private ConvertableGroup<?> group;
	private VElement generator;
	private int k;

	public VerfGroup(String name) {
		this.group=new EllipticCurve(name);
		this.k = group.getOrder().toByteArray().length;
		this.generator = new VElement(group.getGenerator());
	}

	public VerfGroup(BigNumber p, BigNumber q, BigNumber g) {
		this.group=new ModularGroup(p,q,g);
		this.k = p.toByteArray().length;
		this.generator = new VElement(((ModularGroup)group).getGenerator());
	}
	/**
	 * 
	 * @param a
	 * @param b
	 * @return a*b
	 */
	public VElement mult(VElement a, VElement b) throws IllegalArgumentException
	{
		return new VElement(multHelper(group, a.content, b.content));
	}
	/**
	 * @param a
	 * @param k>=0
	 * @return new Element opp(a,a)^k
	 */
	public VElement pow(VElement a, BigNumber k) throws IllegalArgumentException
	{
		return new VElement(powHelper(group, a.content, k));
	}
	/**
	 * @param a
	 * @return new Element opp^-1(a)
	 */
	public VElement inverse(VElement a) throws IllegalArgumentException
	{
		return new VElement(invHelper(group, a.content));
	}
	/**
	 * @param a
	 * @param b
	 * @return new Element opp(a,opp^-1(b))
	 */
	public VElement rightAdj(VElement a, VElement b) throws IllegalArgumentException
	{
		return new VElement(rAdjHelper(group, a.content, b.content));
	}
	/**
	 * 
	 * @param a
	 * @param b
	 * @return true iff a==b
	 */
	public boolean ElementIsEqual(VElement a, VElement b)
	{
		return eqHelper(group, a.content, b.content);

	}
	public String toString() {
		return null;
	}
	/**
	 * @return the groups order
	 */
	public BigNumber getOrder() {
		return group.getOrder();
	}
	/**
	 * @param bt
	 * @return an element with bts value is content
	 */
	public VElement getElement(ByteTree bt) {
		return new VElement(bt);
	}
	/**
	 * @param bt
	 * @return an Array of elements with bts value is content
	 */
	public Array<VElement> getArrayOfElementsFromBT(ByteTree bt) {
		Array<VElement> arr=new Array<VElement>();
		Array<ConvertableGroup<?>.BTElement> temp=group.getElemArr(bt);
		for (ConvertableGroup<?>.BTElement element : temp) {
			VElement n=new VElement(element);
			arr.add(n);
		}
		return arr;
	}
	/**
	 * @param element
	 * @return a ByteTree with element's value as its content
	 */
	public ByteTree toByteTree(VElement element) {
		return element.toByteTree();
	}
	/**
	 * @param element
	 * @return a ByteTree with elements's value as its content
	 */
	public ByteTree toByteTree(Array<VElement> elements)
	{
		ByteTree res = ByteTree.generateEmptyNode();
		for(VElement element : elements){
			res.addChild(toByteTree(element));
		}
		return res;
	}
	/**
	 * @return the groups ByteTree representation of the group
	 */
	public ByteTree getByteTreeRep() {
		return group.getByteTreeRep();
	}
	/**
	 * @return the group's generator
	 */
	public VElement getGenerator() {
		return this.generator;
	}
	/**
	 * @param N
	 * @param prg
	 * @param nr
	 * @return An Array of random Group Elements.
	 * @throws InputMismatchException
	 */
	public Array<VElement> randomArray(int N, PRG prg, int nr) throws InputMismatchException {
		Array<VElement> arr=new Array<VElement>();
		Array<ConvertableGroup<?>.BTElement> temp=group.randomArray(N, prg, nr);
		for (ConvertableGroup<?>.BTElement element : temp) {
			VElement n=new VElement(element);
			arr.add(n);
		}
		return arr;
	}
	
	private <T> ByteTree multHelper(ConvertableGroup<T> group, ByteTree e1, ByteTree e2) {
		return group.grp_opp(group.getElement(e1), group.getElement(e2)).getByteTree();
    }
	private <T> ByteTree powHelper(ConvertableGroup<T> group, ByteTree e1, BigNumber k) {
		return group.grp_opPow(group.getElement(e1), k).getByteTree();
    }
	private <T> ByteTree invHelper(ConvertableGroup<T> group, ByteTree e1) {
		return group.grp_opInverse(group.getElement(e1)).getByteTree();
    }
	private <T> ByteTree rAdjHelper(ConvertableGroup<T> group, ByteTree e1, ByteTree e2) {
		return group.grp_opRightAdj(group.getElement(e1), group.getElement(e2)).getByteTree();
    }
	private <T> Boolean eqHelper(ConvertableGroup<T> group, ByteTree e1, ByteTree e2) {
		return group.ElementIsEqual(group.getElement(e1), group.getElement(e2));
    }
	/**
	 * @return the group Unit
	 */
	public VElement getUnit(){
		return new VElement (group.getGrpMultUnit());
	}
	public class VElement{
		ByteTree content;
		
		private VElement(ConvertableGroup<?>.BTElement elem) {
			this.content = elem.getByteTree();
		}

		private VElement(ByteTree bt) {
			this.content =  bt;
		}
		public ConvertableGroup<?>.BTElement getValue()
		{
			return VerfGroup.this.group.getElement(content);
		}
		public ByteTree toByteTree()
		{
			ByteTree bt;
			if (content.getAmountOfDirectChildren() == 2){ // case affine point
				bt = ByteTree.generateEmptyNode();
				bt.addChild(ByteTree.generateNewLeaf(
						kPadding(content.getTheNChild(0))));
				bt.addChild(ByteTree.generateNewLeaf(
						kPadding(content.getTheNChild(1))));
				
			} else{ // case element of mult-group
				bt = ByteTree.generateNewLeaf(kPadding(content));
			}
			return bt;
		}
	}
	
	public byte[] kPadding(ByteTree bt) {
		byte[] ret = new byte[this.k];
		int zeros = this.k - bt.getSizeOfData();
		for(int i=0; i<zeros ;i++)
			ret[i] = 0;
		System.arraycopy(bt.getTreeData(), 0, ret, zeros, bt.getSizeOfData());
		return ret;
	}
}
